package com.zhuhjay.system.aspectj;

import com.zhuhjay.common.annotation.OperationLog;
import com.zhuhjay.common.domain.ResponseJson;
import com.zhuhjay.common.exception.CustomException;
import com.zhuhjay.common.utils.ClientUtils;
import com.zhuhjay.common.utils.JwtUtils;
import com.zhuhjay.common.utils.ServletUtils;
import com.zhuhjay.system.domain.entity.LoginLog;
import com.zhuhjay.system.domain.entity.OperatorLog;
import com.zhuhjay.system.domain.entity.User;
import com.zhuhjay.system.factory.entity.LoginLogBuilderFactory;
import com.zhuhjay.system.factory.entity.OperatorLogBuilderFactory;
import com.zhuhjay.system.service.ILoginLogService;
import com.zhuhjay.system.service.IOperatorLogService;
import com.zhuhjay.system.service.IUserService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 日志记录的切面 AOP
 * @author ZhuHJay
 * @date 2022/3/26 15:23
 */
@Aspect
@Component
@Order(2)
public class OperationLogAspect {

    private final ILoginLogService loginLogService;
    private final IOperatorLogService operatorLogService;
    private final IUserService userService;

    public OperationLogAspect(ILoginLogService loginLogService,
                              IOperatorLogService operatorLogService,
                              IUserService userService){
        this.loginLogService = loginLogService;
        this.operatorLogService = operatorLogService;
        this.userService = userService;
    }

    /** 配置切入点 **/
    @Pointcut("@annotation(com.zhuhjay.common.annotation.OperationLog)")
    public void pointCut() {}

    /** 后置通知需要指定参数中用于接收方法返回值的参数 **/
    @AfterReturning(value = "pointCut()", returning = "response")
    public void afterReturning(JoinPoint joinPoint, Object response){
        // 从切面织入点处通过反射机制获取织入点处的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        ResponseJson responseJson = (ResponseJson) response;
        /* 操作日志的构建工厂 */
        OperatorLogBuilderFactory operatorLogBuilder = new OperatorLogBuilderFactory();

        // 判断当前是否是登录, 记录登录日志
        if( isLogin(joinPoint) ){
            operatorLogBuilder.operName(loginLogHandler(responseJson));
        }else{
            operatorLogBuilder.operName(ServletUtils.getParameter("username"));
        }

        // 判断是否需要记录 返回结果
        OperationLog opLogAnnotation = method.getAnnotation(OperationLog.class);
        if( opLogAnnotation.hasResult() ){
            operatorLogBuilder.operResult(ServletUtils.writeObjectAsString(response));
        }else{
            operatorLogBuilder.operResult(ServletUtils.writeObjectAsString(ResponseJson.success()));
        }

        operatorLogBuilder.status(ClientUtils.requestStatus((Integer) responseJson.get("status")));
        // 操作日志处理器
        operationLogHandler(method, operatorLogBuilder);
    }

    /** 异常通知需要指定参数中用于获取异常的参数 **/
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void throwing(JoinPoint joinPoint, Throwable exception){
        // 从切面织入点处通过反射机制获取织入点处的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        /* 操作日志的构建工厂 */
        OperatorLogBuilderFactory operatorLogBuilder = new OperatorLogBuilderFactory();
        operatorLogBuilder
                    .errorMsg(exception.toString())
                    .operResult(exception.getMessage())
                    .operName(ServletUtils.getParameter("username"))
                    .status(0);
        operationLogHandler(method, operatorLogBuilder);
    }

    /** 登录日志的记录, 返回登录用户的用户名 **/
    private String loginLogHandler(ResponseJson responseJson){
        LoginLogBuilderFactory loginLogBuilder = new LoginLogBuilderFactory();
        // 判断是否登录成功
        Integer status = ClientUtils.requestStatus((Integer) responseJson.get("status"));
        loginLogBuilder.status(status);
        if( status == 1 ) {
            try {
                // 根据响应数据的token来获取登录用户的信息
                User userInfo = getLoginUserInfoByToken(responseJson);
                loginLogBuilder
                        .username(userInfo.getUsername())
                        .loginIpAndLocation(userInfo.getLoginIp());
            }catch (Exception e){
                e.printStackTrace();
            }
        } else {
            loginLogBuilder
                    .username(getLoginUsernameBySession())
                    .loginIpAndLocation(ClientUtils.getIpAddress(ServletUtils.getRequest()));
        }
        LoginLog log = loginLogBuilder.message((String) responseJson.get("message")).build();
        try{
            loginLogService.save(log);
        }catch (Exception e){
            throw new CustomException("登录日志记录失败: " + e.getMessage());
        }
        return log.getUsername();
    }

    /** 操作日志的常规数据处理 **/
    private void operationLogHandler(Method method, OperatorLogBuilderFactory operatorLogBuilder){
        try{
            // 获取 @OperationLog 的方法描述
            OperationLog opLogAnnotation = method.getAnnotation(OperationLog.class);
            // 获取请求参数数据, 通过请求的url
            String requestUri = ServletUtils.getRequest().getRequestURI();
            OperatorLog operatorLog = operatorLogBuilder
                    .methodName(method.getName())
                    .methodDesc(opLogAnnotation.operaDesc())
                    .requestMethod(ServletUtils.getRequest().getMethod())
                    .operUrl(requestUri)
                    .operIpAndLocation(ClientUtils.getIpAddress(ServletUtils.getRequest()))
                    .operParam((String) ServletUtils.getSession().getAttribute(requestUri))
                    .build();
            ServletUtils.getSession().setAttribute(requestUri, null);
            operatorLogService.save(operatorLog);
        }catch (Exception e){
            throw new CustomException("操作日志记录失败: " + e.getMessage());
        }
    }

    /** 判断是否为登录控制器 **/
    private boolean isLogin(JoinPoint joinPoint){
        return joinPoint.getSignature().getName().startsWith("login")
                && ServletUtils.getRequest().getRequestURI().contains("login");
    }

    /** 获取当前登录用户的信息 **/
    private User getLoginUserInfoByToken(ResponseJson responseJson) throws Exception{
        // 根据响应数据的token来获取登录用户的信息
        String username = JwtUtils.verifyToken((String) ((Map) responseJson.get("data")).get("token"));
        return userService.getUserByUsername(username);
    }

    /** 使用正则表达式从session中的json获取登录的用户名 **/
    private String getLoginUsernameBySession(){
        // 正则表达式
        final String patternString = "\"username\":\"([^\"]+)\"";
        Pattern pattern = Pattern.compile(patternString);
        Matcher matcher = pattern.matcher(
                (CharSequence) ServletUtils.getSession().getAttribute(
                        ServletUtils.getRequest().getRequestURI()
                ));
        return matcher.find() ? matcher.group(1) : "";
    }

}
